﻿# Соглашения о кодировании Ars Magna

Настоящий документ регламентирует соглашения о кодировании проекта Ars Magna для исходных текстов на языке C#. Приведенные в нем рекомендации призваны помочь разработчикам решить проблему выбора между возможными вариантами кодирования, унифицировать исходные тексты и тем самым облегчить их восприятие. Мы надеемся, что код, соответствующий Соглашению, будет более надежным и читаемым.

См. файл `.editorconfig` в корне проекта.

**Важное уточнение:** данные соглашения распространяются на код в библиотеках (папка `Libs`) и приложения (папка `Apps`).

### Обозначения, принятые в документе

В некоторых примерах кода с целью лучшей иллюстрации значащие пробелы заменяются на символ `∙` (точка посреди строки).

# Физическое размещение исходных файлов

## Один тип — один файл

Каждый тип (класс, структура, перечисление, интерфейс или делегат) помещается в отдельный файл. Имя файла должно совпадать с именем типа. Таким образом, для имен файлов исходных текстов применяется те же правила именования, что и для классов (естественно, за исключением угловых скобок). Расширение `.cs` всегда в нижнем регистре. Для дополнительных расширений (например, `.generated`) желательно также применять нижний регистр, кроме тех случаев, когда отступления от правила диктуются технологией.

Для шаблонных классов может потребоваться указание количества типов-аргументов: "SomeClass.cs" означает отсутствие типов-аргументов, "SomeClass\`1.cs" означает один тип-аргумент, "SomeClass\`1.cs" -- два и т. д.

**Исключения:** `partial`-классы, в которых, например, физически разделены части, написанные вручную и сгенерированные инструментально.

Для файлов исходных текстов применяется расширение `.cs` (в нижнем регистре).

Сгенерированные исходные тексты должны иметь двойное расширение `.generated.cs`.

## Дерево каталогов

Исходные тексты помещаются в папку `Source` внутри папки проекта. Группа связанных по смыслу файлов помещается в отдельный подкаталог.

Для каждого уровня пространства имен создается подкаталог. Например, для `AM.Marc.Schema` создаются подкаталог `AM/Marc/Schema`.
Но не каждый подкаталог означает введение пространства имён. Например, в отдельный каталог могут быть вынесены классы отчётов или близкие по смыслу бизнес-сущности.

# Общее оформление исходных текстов

## Кодировка

Исходные тексты хранятся в кодировке UTF-8 (кодовая страница 65001). Обязательно наличие сигнатуры Unicode (`EF BB BF`). В качестве перевода строки в системе Windows используется пара символов `CR`/`LF` (0x000D/0x000A). При помещении в репозиторий Git происходит замена на символ `LF` (0x000A). В операционных системах из семейства Unix используется перевод строки `LF` (0x000A).

## Шапка файла

Файл исходного текста должен начинаться с комментария следующего вида:

```c#
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace

/* FileName.cs -- очень краткое описание
 * Ars Magna project, https://arsmagna.ru
 */

#region Using directives

using System;
...

#endregion

namespace SomeNamespace;
```

Первые две строки позволяют запускать на исходных кодах проекта бесплатную версию статический анализатор PVS-Studio, что способствует повышению качества кода в проекте.

Строки, начинающиеся с `// Resharper` служат для управления выдачей предупреждений в IDE, например, в JetBrains Rider, Fleet или Visual Studio с установленным плагином Resharper.

Краткое описание должно быть на русском языке и занимать не более одной строки. Пояснения должны быть вынесены в отдельный блок комментариев ниже. Точка в конце пояснения не ставится

Пример:

```c#
/* BinaryAttachment.cs -- аттачмент к исключению
 * Ars Magna project, https://arsmagna.ru
 */
```

Регион с подключением пространств имен (если таковые имеются), должен оформляться с помощью соответствующей пары директив.

Пространство имен должно объявляться file-scoped. Это экономит отступы и делает текст более читаемым.

## Форматирование исходных текстов

### Табуляция и отступы

Для создания отступов используются символы пробела. Принят отступ 4 знакоместа. Замена на символы табуляции недопустима.

Логический отступ (означающий вложенную конструкцию) формируется добавлением 4 пробелов.

### Пробелы

Пробелы ставятся:
* между операторами;
* между операторами и операндами;
* после запятой, точки с запятой;
* перед и после скобок (круглых и квадратных). Исключения: 1) пустые пары скобок () и [], 2) квадратные скобки в объявлениях массивов, например `object[]`;
* перед и после двоеточия, исключение составляют метки (в т.ч. в операторе `switch`).

Пробелы не ставятся:
* перед запятой, точкой.

Пример:
```c#
z∙=∙x∙+∙y;
for∙(∙int∙i∙=∙0;∙i∙<∙list.Length;∙i++∙)
{
∙∙∙∙list∙[∙i∙].Dump∙();
}
```

### Перенос строк

Желательно избегать строк длиной более 80-символов, для длинных строк использовать перенос. Строку можно переносить в следующих позициях:
* после запятой;
* перед оператором.

Перенесенная строка по отношению к своему началу должна иметь дополнительный отступ в два пробельных символа. Оператор (такой, как «сложение», «логическое или» и т.д.) не оставляется последним на строке, а переносится на следующую. Пример:

```c#
result∙=∙"Line begins:∙" ∙+∙var1
∙∙+∙"∙middle∙of∙the∙line∙"
∙∙+∙var2∙+∙"∙(line∙ends).";
```

Для длинных вызовов сложных методов можно использовать следующий метод переноса строк:

```c#
ComplexMethodCall
∙∙(
∙∙∙∙firstArgument,∙//∙комментарий
∙∙∙∙"Second argument",∙//∙другой комментарий
∙∙∙∙thirdArgument
∙∙);
```
При переносе предпочтительнее не разбивать низкоуровневые конструкции, лучше делить строку по границам блоков большего уровня (например, скобок).

```c#
//∙неверно
result∙=∙(∙a∙+∙b∙)∙/∙(∙c∙–∙d
∙∙+∙e∙)∙–∙g∙*∙h;

// верно
result∙=∙(∙a∙+∙b∙)∙/∙(∙c∙–∙d∙+∙e∙)
∙∙-∙g∙*∙h;
```

### Другое форматирование

Открывающая и закрывающая фигурные скобки должны располагаться на отдельных строках. Закрывающая фигурная скобка должна располагаться строго под соответствующей открывающей скобкой.

Код, заключенный между парой фигурных скобок, должен иметь отступ в один символ табуляции.

Желательно, чтобы на одной строке присутствовало не более одного оператора.

Между логически завершенными блоками кода допускается наличие одной пустой строки.

#### «Табличное» форматирование

Где возможно, следует использовать "табличное" форматирование, подчеркивающее логический "ритм" в коде:

```c#
newColor.Red∙∙∙=∙brightColor.Red∙∙∙-∙10;
newColor.Green∙=∙brightColor.Green∙–∙10;
newColor.Blue∙∙=∙brightColor.Blue∙∙-∙10;
```

Однако "табличное" форматирование не должно становиться самоцелью, и может быть опущено, если его поддержание требует слишком много усилий либо уродует код.

## Оформление и именование отдельных элементов языка C#

### Регионы

Весь код должен заключаться в регионы. Названия регионов должны быть на английском языке???

Типичные названия регионов:
* Initialization;
* GUI events handing;
* Override inherited members;

Директива #region отделяется от окружающего кода пустыми строками.

### Подключение пространств имен

Порядок следования пространств имен в `using`-регионе должен быть следующим:
* .NET;
* сторонние сборки;
* собственные библиотеки;
* пространства имен проекта.

Группы директив `using` отделяются друг от друга пустой строкой. Внутри групп элементы упорядочиваются в алфавитном порядке.
Директива `using` должна располагаться вне пространства имен.

### Комментарии

Все комментарии в тексте программ должны быть только на русском языке, ибо целевая аудитория проекта русскоязычная.
Для пространных объяснения каких-либо особенностей кода предпочтительнее использовать многострочный комментарий следующего вида:

```c#
/*
 * Реалиазция алгоритма XYZ,
 * см. Большая Книга Нужных Алгоритмов, С. 100-110
 */
```

Для комментирования "в одну строчку" используется следующий стиль:

```c#
bool hasProblems; // если что-то пошло не так

ComplexMethodCall
    (
        firstArgument, // один комментарий
        "Второй аргумент", // другой комментарий
        thirdArgument
    );
```

Комментарии `//` часто применяются для «закомментирования» (временного исключения) участков кода (см. горячие клавиши среды разработки). Однако для больших кусков кода (более одного метода или более 10 строк) предпочтительнее использовать препроцессор:

```c#
    x = 1.0; // Initialize variables
    y = 2.0;
 //	z = x.op_Add ( y ); // Strange code, isn’t
    z = x + y;
#if NOTDEFINED
    // this code is too buggy
    result = Math.Atan2 ( z, x + y ); // excepton will be raised
#endif
```

Обратите внимание, что `//`, «закомментирующие» код, помещаются с начала строки, в отличие от обычного однострочного комментария.
Также применяются однострочные комментарии для аннотации кода (они автоматически помещаются окно задач среды разработки) следующего вида:

```c#
// TODO проверить блокировку записей
// TODO убедиться, что файл существует
```

Кроме аннотации `TODO` допускается применение аннотаций `HACK`, и `UNDONE`.
В стабильную версию Ars Magna не должны попадать большие куски закомментированного кода – перед коммитом в trunc их желательно удалять.

#### Комментарии для самодокументирования

Комментарии для самодокументирования также должны быть только на русском языке.

Заполнение следующих тегов самодокументирования обязательно: `<summary>`, `<param>`, `<returns>`.

```c#
/// <summary>
/// Добавляет в конец блока разметки указанный текст несколько раз.
/// </summary>
/// <param name="text">Добавляемый текст.</param>
/// <param name="ntimes">Необходимое количество добавлений.</param>
/// <returns>Объект разметки с добавленным текстом.</returns>
public MarkupText Repeat
    (
        string text,
        int ntimes
    )
{
    Sure.NonNegative (ntimes);

    var builder = new StringBuilder();
    for (; ntimes > 0; ntimes--)
    {
        builder.Append (text);
    }

    if (builder.Length > 0)
    {
        SubPieces.Add (builder.ToString());
    }

    return this;
}
```

### Общие правила именования для идентификаторов

Индикаторы должны содержать следующие символы:

* Буквы базового набора латиницы (прописные и строчные);
* Арабские цифры;
* Знак подчеркивания.

Другие символы в идентификаторах недопустимы. В частности, нельзя называть типы, методы и переменные по-русски (несмотря на то, что C# позволяет это). Нельзя использовать символ подчеркивания в публичных идентификаторах (чтобы не вызывать проблем у пользователей VisualBasic.NET и других ущербных языков).

В проекте Ars Magna не применяется венгерская нотация для префиксов, означающих тип параметра, поля или переменной.

По возможности следует избегать аббревиатур или сокращений в качестве частей имен идентификаторов. Например, вместо GetWin предпочтительнее использовать GetWindow. Если все же используются аббревиатуры, то аббревиатуры длиной не более двух символов следует капитализировать (например, AM.UI), а более длинные — давать в нотации Pascal или Camel (например, RtfFormatProvider).

Следует избегать использования идентификаторов, конфликтующих с ключевыми словами языков C#, VB.NET, VC++.NET, J# и JScript.NET. По крайней мере, такие идентификаторы не должны быть видны "наружу".

Примерный список ключевых слов:

`AddHandler`, `AddressOf`, `Alias`, `And`, `Ansi`, `As`, `Assembly`, `async`, `Auto`, `await`, `Base`, `Boolean`, `ByRef`, `Byte`, `ByVal`, `Call`, `Case`, `Catch`, `CBool`, `CByte`, `CChar`, `CDate`, `CDec`, `CDbl`, `Char`, `CInt`, `Class`, `CLng`, `CObj`, `Const`, `CShort`, `CSng`, `CStr`, `CType`, `Date`, `Decimal`, `Declare`, `Default`, `Delegate`, `Dim`, `Do`, `Double`, `Each`, `Else`, `ElseIf`, `End`, `Enum`, `Erase`, `Error`, `eval`, `Event`, `Exit`, `extends`, `ExternalSource`, `False`, `Finalize`, `Finally`, `Float`, `For`, `Friend`, `Function`, `Get`, `GetType`, `Goto`, `Handles`, `If`, `Implements`, `Imports`, `In`, `Inherits`, `instanceof`, `Integer`, `Interface`, `Is`, `Let`, `Lib`, `Like`, `Long`, `Loop`, `Me`, `Mod`, `Module`, `MustInherit`, `MustOverride`, `MyBase`, `MyClass`, `Namespace`, `New`, `Next`, `Not`, `Nothing`, `NotInheritable`, `NotOverridable`, `Object`, `On`, `Option`, `Optional`, `Or`, `Overloads`, `Overridable`, `Overrides`, `package`, `ParamArray`, `Preserve`, `Private`, `Property`, `Protected`, `Public`, `RaiseEvent`, `ReadOnly`, `ReDim`, `Region`, `REM`, `RemoveHandler`, `Resume`, `Return`, `Select`, `Set`, `Shadows`, `Shared`, `Short`, `Single`, `Static`, `Step`, `Stop`, `String`, `Structure`, `Sub`, `SyncLock`, `Then`, `Throw`, `To`, `True`, `Try`, `TypeOf`, `Unicode`, `Until`, `Val`, `volatile`, `When`, `While`, `With`, `WithEvents`, `WriteOnly`, `Xor`

### Пространства имен

Для пространств имен используется нотация Pascal.

Все типы должны быть заключены в пространства имен. Не должно быть типов, не заключенных в пространство имен.

К имени пространства имен, содержащего типы, которые обеспечивают функциональность времени разработки для основного пространства имен, добавляется суффикс `.Design`. Например, `AM.Library.Loans.Design`.

Вложенные пространства имен должны иметь зависимость от типов содержащего их пространства имен. Например, типы в `AM.Library.Loans.Design` должны зависеть от `AM.Library.Loans`.

Предпочтительнее имена пространств имен во множественном числе, если это семантически оправданно. Например, лучше использовать `AM.Library.Loans`, чем `AM.Library.Loan`. Также можно использовать суффикс `ing`, например, `ManagedIrbis.Searching`.

### Шаблон объявления типов

Пример (обратите внимание на использование пробелов вокруг двоеточия):

```c#
class Loan
  : LibrarySubdivision,
  IInspectable
{

#region Class fields

    private string name;

    public string Name
    {
        get
        {
            return name;
        }
    }

#endregion

#region Initialization

    public Loan ( string name ) : base ()
    {
        this.name = name;
    }

#endregion

#region IInspectable members

    private bool IInspectable.Inspect ()
    {
        return true;
    }

#endregion

}
```

## Классы

В качестве имен классов используются существительные или именные группы. Применяется нотация Pascal.

Нельзя использовать префиксы типа, например, называть класс `CBook` (правильно — просто `Book`).

Следует избегать использования имен классов, которые дублируют общепринятые пространства имен .NET. Например, не используйте в качестве имен классов следующие имена: `System`, `Collections`, `Forms` или `UI`.

Нельзя использовать одинаковые имена для класса и пространства имен.

## Структуры

Правила именования для структур такие же, как и для классов (см. выше).

## Перечисления

Правила именования для перечислений такие же, как и для классов (см. выше).

Нельзя использовать суффикс Enum в имени перечислимого типа.

Если перечисление является битовым полем (т.е. имеет атрибут `FlagsAttribute`), его имя должно быть по возможности во множественном числе, иначе — в единственном.

В качестве имен для членов перечислений используются существительные или именные группы, кроме тех случаев, когда соответствующие понятия предметной области не являются существительными, и с этим ничего поделать невозможно.

Пример:

```c#
public enum HttpMethods
{
    Get,
    Put,
    Post
}
```

Применяется нотация Pascal.

## Интерфейсы

Правила именования интерфейсов такие же, как и для классов (см. выше), но имя всегда предваряется заглавной латинской буквой I.

Дополнительно в качестве имен интерфейсов можно использовать прилагательные, описывающие поведение класса, реализующего интерфейс. Например: `IPersistable` или `IFormatable`.

## Записи

Там, где это имеет смысл, вместо классов и структур следует применять записи, т. к. они избавляют от шаблонного кода и создают неизменяемые объекты.

```csharp
public record Person (string FirstName, string LastName);

var alexey = new Person ("Alexey", "Mironov");
var alexander = alexey with { Name = "Alexander" };
```

## Порядок следования членов типа

Порядок следования членов типа должен быть следующим:

* вложенные типы;
* константы;
* события;
* поля класса;
* свойства класса;
* публичные методы;
* защищенные (protected) и внутренние (internal) методы;
* приватные (private) методы.

Внутри групп свойства, методы и поля упорядочиваются по алфавиту. Для полей, тесно связанных со свойством или методом допускается размещение в непосредственной близости.

Акцессор `private` всегда указывается явно. Не допускаются члены с неуказанным акцессором.

Между членами типа оставляется пустая строка. Для связанного набора полей или для свойства и соответствующего ему поля пустую строку можно опускать.

Конструкторы экземпляров и перегруженные методы рекомендуется располагать последовательно в порядке нарастания сложности:

```c#
public class SampleClass
{
    public SampleClass()
      : this ( “abc”, 1 )
    {
        // пустое тело конструктора
    }

    public SampleClass
        (
            string firstPar
        )
        : this (firstPar, 1)
    {
        // пустое тело конструктора
    }

    public SampleClass
        (
            string firstPar,
            int secondPar
        )
    {
        // действия по инициализации экземпляра
    }
}
```

## Свойства классов/структур/интерфейсов

В качестве имен свойств используются существительные или именные группы. Применяется нотация Pascal.

## Поля классов/структур

В качестве имен полей используются существительные или именные группы.

Для публичных (и защищенных) полей применяется нотация Pascal, для остальных — Camel.

Имя приватного поля (с акцессором private) должно начинаться с символа подчеркивания.

## Константы

В качестве имен для констант используются существительные или именные группы. Исключение - случаи, когда соответствующие понятия предметной области не являются существительными, и с этим ничего поделать нельзя.

Пример (обратите внимание на "табличное" выравнивание):

```c#
class Color
{
    const string Red   = "#FF0000";
    const string Green = "#00FF00";
    const string Blue  = "#0000FF";
}
```

Применяется нотация Pascal.

## Методы классов/структур/интерфейсов

### Собственно методы

В качестве имен методов используются глаголы или глагольные группы. Применяется нотация Pascal. Имя приватного метода (с акцессором private) должно начинаться с символа подчеркивания.

Примеры:

```c#
void RemoveAll ();

char[] ReadChars (int howMany);

void Invoke ();
```

### Параметры методов

В качестве имен параметров используются существительные или именные группы. Применяется нотация Camel. Не используется венгерская нотация для префиксов.

Зарезервированные параметры не используются.

Имена параметров должны быть достаточно описательными, чтобы по имени параметра и его типа в большинстве случаев можно было догадаться о его назначении. Имена должны описывать значения параметров, а не их тип.

### Блок проверок

Публичные (public) и защищенные (protected) методы должны проверять свои аргументы, т. к. могут быть вызваны посторонним кодом.

Пример:

```csharp
void SomeMethod
    (
        SynchronousConnection conneciton,
        string command,
        CommandMode mode,
        int retryCount
    )
{
    Sure.NotNull (connection);
    Sure.NotNullNorEmpty (command);
    Sure.Defined (mode);
    Sure.NonNegative (retryCount);

    ...
}
```

Приватные и защищенные методы могут не проверять аргументы.

### Вызов сервисов

Предполагайте, что любой (внешний) сервис может вернуть ошибку 5хх, мусор, или выдать таймаут.

Предполагайте, что любой файл на диске может отсутствовать, иметь неверную настройку контроля доступа, неверной длины или содержать мусор.

Предполагайте, что любая периферия (например, USB-порт) может вернуть ошибку, мусор или выдать таймаут.

### Локальные переменные

В качестве имен локальных переменных используются существительные или именные группы. Применяется нотация Camel. В качестве целочисленного счетчика в циклах рекомендуется использовать переменные с именами `i`, `j`, `k`, `l`, `m`, `n`. На одной строке должно быть не более одного объявления локальной переменной. Пример:

```c#
int level; // глубина вложенности
int tableSize; // размер таблицы в байтах

for (var i = 0; i < level; i++)
{
    ...
}
```

Не допускается размещать в одной строке два и более объявления переменных. Локальные переменные отделяются от остального кода метода одной пустой строкой (см. пример ниже).

Объявлять переменные желательно как можно ближе к месту их использования, при объявлении желательно инициализировать их:

```c#
var hoursWorked = thisMonth.Hours + previousMonth.Hours;

if (hoursWorked < minimumAllowed)
{
    manager.WarnAboutHours (worker, hoursWorked);
}
```

Везде, где возможно, следует использовать `var` - автоматическое выведение типа локальной переменной.

### Тело метода

Короткое тело метода/свойства, особенно, возвращающего тривиальный результат, может быть записано в виде лямбды:

```c#
bool UsesExtendedNotation() => true;
```

### Открытость/закрытость для наследования

Класс не предназначен явно для наследования, если:

* в нём нет защищённых (protected) членов;
* в нём нет виртуальных (virtual) методов/свойств;
* в нём нет абстрактных (abstract) методов/свойств.

Подобные классы следует снабжать модификатором `sealed`, чтобы подчеркнуть для прикладного программиста, что данный класс не проектировался как наследуемый.

"Запечатывание" класса позволяет JIT применить оптимизации, которые положительно сказываются на производительности программы, поэтому следует "запечатывать" классы (или отдельные методы/свойства) везде, где это имеет смысл.

### Модификатор readonly

Методы, которые не меняют состояние объекта, должны быть помечены ключевым словом `readonly`

```c#
public struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    public readonly double Distance => Math.Sqrt(X * X + Y * Y);

    public readonly override string ToString() =>
        $"({X}, {Y}) is {Distance} from the origin";
}
```

### Локальные функции

По возможности локальные функции, не захватывающие контекст, следует снабжать модификатором `static`:

```c#
int Method()
{
    var y = 5;
    var x = 7;
    return Add (x, y);

    static int Add (int left, int right) => left + right;
}
```

### Лямбда-функции

По возможности лямбда-функции, не захватывающие контекст, следует снабжать модификатором `static`:

```c#
int Method (IEnumerable<TItem> items)
{
    ...
    return items.Sum (static it => it * 2);
}
```

Лямбда-функции, захватывающие контекст, желательно помечать комментарием:

```c#
Start (/* capturing */ context =>
{
    var batch = BatchRecordReader.WholeDatabase
        (
            connection, // захваченная переменная
            database: connection.EnsureDatabase(),
            batchSize: 1_000
        );
    context.AddBatch (batch);
});
```

### Асинхронные методы

Имя асинхронного метода должно иметь суффикс `Async`, возвращаемое значение должно быть одного из трех типов:

* `System.Threading.Tasks.Task` или `System.Threading.Tasks.Task<T>`,
* `System.Threading.Tasks.ValueTask` или `System.Threading.Tasks.ValueTask<T>`,
* `void` (не рекомендуется, но в ряде случаев неизбежно, например, в обработчиках событий WinForms).

Рекомендуется снабжать асинхронные методы модификатором async, если в их теле выполняются асинхронные операции:

```c#
public async Task<string> GetSomeContent()
{
    var httpClient = _httpClientFactory.CreateClient();

    var result = await httpClient.GetStringAsync ("http://webcode.me");

    return result;
}
```

При проектировании асинхронного метода рекомендуется предусмотреть передачу в него одним из последних аргументов `System.Threading.CancellationToken`, который в свою очередь необходимо передавать в вызываемые асинхронные методы

```c#
async Task<string> GetSomeContent
    (
        string address,
        CancellationToken token = default
    )
{
    // как-нибудь обрабатываем отмену операции, например, так
    token.ThrowIfCancellationRequested();

    var httpClient = _httpClientFactory.CreateClient();
    var result = await httpClient.GetStringAsync (address, token);

    return result;
}
```

Везде, где это имеет смысл, рекомендуется для получения результата асинхронной операции использовать `await` вместо `.Result` или `Wait()`.

## Производные типы

### Атрибуты

#### Классы атрибутов

Имя класса атрибута всегда должно содержать суффикс `Attribute`. Класс атрибута должен быть наследником `System.Attribute`. Остальные требования такие же, как и у обычных классов (см. выше).

И наоборот, если имя класса содержит суффикс `Attribute`, этот класс должен быть наследником `System.Attribute`.

Класс атрибута обязательно должен быть снабжен атрибутом `AttributeUsageAttribute`, в конструкторе которого перечисляются все возможные области применения данного атрибута:

```c#
[AttributeUsage (AttributeTargets.Property)]
public sealed class PrintableAttribute
    : Attribute
{
    ...
}
```

"Запечатывайте" (устанавливайте модификатор `sealed`) классы атрибутов везде, где это имеет смысл.

### Исключения

Имя класса исключения всегда должно содержать суффикс `Exception`. Класс исключения должен быть наследником `System.ApplicationException`. Остальные требования такие же, как и у обычных классов (см. выше).

И наоборот, если имя класса содержит суффикс `Exception`, он должен быть наследником `System.Exception`.

### Делегаты

В качестве имен классов делегатов используются глаголы или глагольные группы. Применяется нотация Pascal. Суффикс Delegate не добавляется.

### События

#### Классы событий

В качестве имен классов событий используются глаголы или глагольные группы. Применяется нотация Pascal.

"Запечатывайте" (устанавливайте модификатор `sealed`) классы событий везде, где это имеет смысл.

#### Обработчики событий

В качестве имен обработчиков событий используются глаголы или глагольные группы. Применяется нотация Pascal.

Имя обработчика события должно содержать суффикс `EventHandler`. И наоборот, если имя класса содержит суффикс `EventHandler`, он должен быть наследником `System.EventHandler`.

В обработчике события должны быть определены два параметра: `sender` и `eventArgs`. Первый параметр всегда должен иметь тип `object?`, даже если есть возможность использовать более специфический тип. Второй параметр должен содержать информацию о событии и принадлежать соответствующему специфическому типу.

Пример обработчика события:

```c#
public delegate void MouseEventHandler
  (
    object? sender,
    MouseEventArgs eventArgs
  );
```

#### Аргументы событий

В имени класса аргумента события должен присутствовать суффикс `EventArgs`. И наоборот, если имя класса содержит суффикс `EventArgs`, он должен быть наследником `System.EventArgs`.

Пример:

```c#
public sealed class MouseEventArgs
    : EventArgs
{
    public int X { get; set; }
    public int Y { get; set; }
}
```

Имена событий «до» должны содержать префикс `Before`, событий «после» — префикс `After`. Например: `BeforeClose`, `AfterClose`.

На каждое публичное событие должен быть обеспечен protected/private метод с именем `OnXxx` (или `RaiseXxx`). Этот метод должен иметь только параметр `eventArgs`, т.к. отправителем всегда является данный экземпляр типа.

```c#
public sealed class SomeClass
{
    public event MouseEventHandler? MouseClick;

    private void OnMouseClick
        (
            MouseEventArgs eventArgs
        )
    {
        MouseClick?.Invoke (this, eventArgs);
    }
}
```

### Разрешения

Имя класса разрешения должно содержать суффикс `Permission`. И наоборот, если в имени класса содержится суффикс `Permission`, он должен реализовывать интерфейс `System.Security.IPermission`.

### Контейнеры

Имена классов-контейнеров подчиняются следующим правилам:

| Контейнер | Суффикс имени |
|-----------|---------------|
| Стек      | Stack         |
| Очередь   | Queue         |
| Словарь   | Dictionary    |
| Коллекция | Collection    |
| Список    | List          |

## Оформление синтаксических конструкций

### Оператор if

Блоки кода в операторе if/else обязательно заключаются в фигурные скобки (даже состоящие из единственного оператора).
Группы в булевом выражении заключаются в скобки. Например:

```c#
if (( a == 0 ) && ( b == 0 ))
{
    // some code here
}
```

Пример сложного оператора if:

```c#
if (reader.IsDebtor ())
{
    HandleDebtorReader (reader);
}
else if (reader.IsBlocked ())
{
    HandleBlockedReader (reader);
}
else
{
    HandleNormalReader (reader);
}
```

### Циклы for и foreach

Блок кода циклов `for` и `foreach` обязательно должен заключаться в фигурные скобки.

Условное выражение в цикле `for` записывается по тем же правилам, что и в операторе `if` (см. выше).

Пример оформления цикла foreach:

```c#
foreach (var author in allAuthors)
{
    authorIndex.Add (author);
    Console.WriteLine (author.Name);
}
```

Везде, где это имеет смысл, следует использовать `var`-определение переменной цикла.

### Цикл while

Условное выражение в цикле `while` записывается по тем же правилам, что и в операторе `if` (см. выше).

### Оператор switch

Пример оформления оператора switch:

```c#
switch (condition)
{
    case 1:
        // some code
        break;

    case 2:
        // some code
        break;

    default:
        // some code
        break;
}
```

Многострочные `case` следует отделять друг от друга пустой строкой.

Везде, где это имеет смысл, следует использовать вместо оператора выражение `switch`:

```c#
var answer = question switch
{
    "How are you?" => "Fine",
    "What time is it now?" => DateTime.Now,
    _ => throw new InvalidQuestionException()
}
```

### try/catch

Пример оформления блока `try/catch`:

```c#
try
{
    CallWiseCode();
}
catch (MyException exception)
{
    _logger.LogError (exception);
}
```

Вариант без обработки (подавление исключений):

```c#
try
{
    CallWiseCode();
}
catch
{
    // подавление исключений
}
```

Обратите внимание на явное указание подавления исключений.

### Пустой блок оператора

Пустой блок оператора должен снабжаться специальным комментарием (чтобы было понятно, что код не забыт):

```c#
while (!queue->TryGetNext())
{
    // пустой блок
}
```

### Оператор using

Блок кода оператора using обязательно должен заключаться в фигурные скобки. Пример:

```c#
using (var reader = new MarcIsoRecordReader (file))
{
    var marcRecord = reader.ReadRecord();
    ...
}
```

Везде, где это имеет смысл, следует использовать `var`-декларацию переменной. Кроме того, имеет смысл применять `using`-декларацию переменной, экономящую логический отступ и повышающую читаемость кода:

```c#
using var reader = new MarcIsoRecordReader (file);
var marcRecord = reader.ReadRecord();
...
```

# Нумерация версий

В этом разделе должны быть описаны правила нумерации версий.

# Прочие замечания

## Именование элементов управления

Для элементов управления предпочтительным является следующий стиль именования:

```c#
Button  cancelButton;
TextBox nameTextBox;
Panel   upperPanel; // и т. д.
```

## Конструкторы экземпляров классов

### Функциональность конструкторов

Сведите к минимуму работу, выполняемую в конструкторе. Конструкторы должны только «раскладывать» свои параметры по \[приватным\] полям экземпляра.

### Метод Initialize

Реальную инициализацию экземпляра (если инициализация отложенная, т. е. осуществляется не в конструкторе) желательно оформлять в виде защищенного метода `Initialize`:

```c#
protected void Initialize ()
{
    // действия по инициализации экземпляра
}
```

Кроме того, в метод `Initialize` рекомендуется выносить общий функционал перегруженных конструкторов, если не удаётся оформить её через взаимные вызовы конструкторов.

Такая практика помогает легко находить, где происходит инициализация объекта (кроме того, инициализационный код собирается в одном месте).

Классы, экземпляры которых не должны создаваться клиентским кодом

```c#
public sealed class LoanDepartment
{
    // internal constructor prevents the class
    // from being created by client code.
    internal LoanDepartment
    {
        // Some code may be here
    }
}
```

## Очистка объекта

Рекомендуемая очистка объекта

```c#
public class MyClass
    : IDisposable
{
    ~MyClass ()
    {
        Dispose ( false );
    }

    public void Dispose ()
    {
        Dispose ( true );
        GC.SuppressFinalize ( this );
    }

    protected virtual void Dispose ( bool disposing )
    {
        if ( disposing )
        {
            ...
        }
        else
        {
            ...
        }
    }
}
```

## Асинхронная очистка

```c#
public class SomeClass
    : IAsyncDisposable
{
    protected virtual ValueTask DisposeAsyncCore()
    {
        // некие действия по очистке
    }

    protected virtual void Dispose (bool disposing)
    {
        if (disposing)
        {
            // освобождение managed-ресурсов
        }
    }

    public async ValueTask DisposeAsync()
    {
        // Perform async cleanup.
        await DisposeAsyncCore().ConfigureAwait (false);

        // Dispose of unmanaged resources.
        Dispose (false);

        #pragma warning disable CA1816 // Dispose methods should call SuppressFinalize

        // Suppress finalization.
        GC.SuppressFinalize (this);

        #pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
    }
}
```

## Пустое тело метода

Если тело метода намерено оставлено пустым, об этом должен быть сделан комментарий

```c#
public virtual void SomeMethod()
{
    // тело метода оставлено пустым
}
```

Если же метод пока не реализован, то должно выбрасываться исключение `NotImplementedException` либо оставлен комментарий `TODO`

```c#
public void FirstMethod()
{
    throw new NotImplementedException();
}

public virtual void SecondMethod()
{
    // TODO реализовать метод
}

```

# Файлы решений и проектов

Имя файла решения подчиняется правилам для типов (см. выше), расширение строго в нижнем регистре.

Файлы проектов -- `csproj`, `Directory.Build.props`, `targets` и прочие аналогичные -- оформляются согласно следующим положениям.

Основное расширение всегда строго в нижнем регистре. Дополнительные расширения (например, `.Build`) в соответствии с применяемой технологией.

Для содержимого файла используется кодировка UTF-8, отступы оформляются четырьмя пробелами, группы отделяются друг от друга пустой строкой.

```msbuild
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
    </PropertyGroup>

    <ItemGroup>
        <ProjectReference Include="..\..\Libs\AM.Scripting\AM.Scripting.csproj" />
        <ProjectReference Include="..\..\Libs\Istu5\Istu5.csproj" />
    </ItemGroup>

    <ItemGroup>
        <None Update="Scripts\HelloWorld.barsik">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
        </None>
    </ItemGroup>

</Project>
```

Группы рекомендуется снабжать краткими комментариями, поясняющими их назначение:

```msbuild
<Project Sdk="Microsoft.NET.Sdk">

    <!-- NuGet related stuff -->
    <PropertyGroup>
        <IsPackable>false</IsPackable>
        <Product>Ars Magna</Product>
        <Company>Ars Magna Project</Company>
        <Authors>Alexey Mironov</Authors>
        <Copyright>Copyright Alexey Mironov 2006-2022</Copyright>
        <PackageLicenseExpression>MIT</PackageLicenseExpression>
        <PackageProjectUrl>https://arsmagna.ru</PackageProjectUrl>
        <RepositoryUrl>https://github.com/amironov73/ManagedIrbis5</RepositoryUrl>
        <PublishRepositoryUrl>true</PublishRepositoryUrl>
    </PropertyGroup>

    <!-- Enable Microsoft SourceLink -->
    <PropertyGroup>
        <EmbedUntrackedSources>true</EmbedUntrackedSources>
        <PublishRepositoryUrl>true</PublishRepositoryUrl>
        <IncludeSymbols>true</IncludeSymbols>
        <SymbolPackageFormat>snupkg</SymbolPackageFormat>
        <Deterministic>true</Deterministic>
        <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
    </PropertyGroup>

</Project>
```

Общие элементы проектов выносятся в файл `Directory.Build.props`.

В проекте Ars Magna применяется централизованный менеджмент NuGet-пакетов, поэтому рядом с файлом решения должен находится файл `Directory.Packages.props`

```msbuild
<Project>

    <PropertyGroup>
        <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
    </PropertyGroup>

    <!-- Централизованное управление пакетами -->
    <ItemGroup>
        <PackageVersion Include="Avalonia" Version="11.0.0-preview3"/>
        <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.0-preview3"/>
        <PackageVersion Include="Avalonia.Desktop" Version="11.0.0-preview3"/>
        <PackageVersion Include="XamlNameReferenceGenerator" Version="1.4.1"/>
    </ItemGroup>

</Project>
```

В файлах же самих проектов версия проекта не указывается:

```msbuild
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <Nullable>enable</Nullable>
        <TrimMode>copyused</TrimMode>
        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
    </PropertyGroup>

    <!-- Подключаем наши кастомные контролы -->
    <ItemGroup>
        <ProjectReference Include="..\..\Libs\AM.Avalonia\AM.Avalonia.csproj" />
    </ItemGroup>

    <!-- Централизованное управление пакетами -->
    <ItemGroup>
        <PackageReference Include="Avalonia"/>
        <PackageReference Include="Avalonia.Desktop"/>
        <PackageReference Include="XamlNameReferenceGenerator"/>
    </ItemGroup>

</Project>
```

# XAML-файлы

Имена файлов, содержащих разметку пользовательского интерфейса, подчиняются тем же правилам, что и имена типов (см. выше), расширения (`.xaml`, `.axaml` и т. д.) строго в нижнем регистре.

```xaml
<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:AM.Avalonia.Dialogs"
        Width="400" Height="150"
        CanResize="False"
        x:Class="AM.Avalonia.Dialogs.InputDialog"
        Title="Введите значение">

    <Design.DataContext>
        <local:InputDialog />
    </Design.DataContext>

    <StackPanel
        Margin="10"
        Orientation="Vertical"
        HorizontalAlignment="Stretch"
        VerticalAlignment="Center">

        <Label
            HorizontalAlignment="Stretch"
            Content="{Binding Prompt}"
            />

        <TextBox
            Margin="0, 10, 0, 0"
            HorizontalAlignment="Stretch"
            Text="{Binding Value}"
            />

        <StackPanel
            Margin="10"
            Orientation="Horizontal"
            HorizontalAlignment="Right">

            <Button
                Name="OkButton"
                Click="OkButton_OnClick"
                >OK</Button>

            <Button
                Name="CancelButton"
                Click="CancelButton_OnClick"
                Margin="3, 0, 0, 0"
                >Отмена</Button>

        </StackPanel>

    </StackPanel>

</Window>
```

# Файлы ресурсов и прочих ассетов

Имена файлов, содержащих ресурсы для пользовательского интерфейса (изображения, иконки, шрифты), подчиняются тем же правилам, что и имена типов (см. выше), расширения (`.bmp`, `.png` и т. д.) строго в нижнем регистре.

# Документы, использованные при составлении Соглашения:

1. Шатохина Н.А. Рекомендации по разработке библиотеки классов. http://www.gotdotnet.ru
2. Krüger M. C# Coding Style Guide. http://www.icsharpcode.net
3. Vorontsov S. aka V©R©N Соглашения о кодировании. http://www.aspnetmania.com
4. Lowy J. C# Coding Standard. http://www.idesign.net
5. Соглашения об оформлении кода команды RSDN http://rsdn.ru
